{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 使用PAI ModelGallery部署和微调Qwen2模型\n",
    "\n",
    "PAI ModelGallery 提供了一系列热门开源模型，并为模型预置了推理和微调训练配置，支持用户在 PAI 上直接部署使用，或是进行微调训练。在本示例中，我们将以[Qwen2-0.5b-Instruct](https://www.modelscope.cn/models/qwen/Qwen2-0.5B-Instruct)模型为示例，介绍通过 PAI Python SDK 使用PAI ModelGallery提供的模型。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 费用说明\n",
    "\n",
    "本示例将会使用以下云产品，并产生相应的费用账单：\n",
    "\n",
    "- PAI-DLC：运行训练任务，详细计费说明请参考[PAI-DLC 计费说明](https://help.aliyun.com/zh/pai/product-overview/billing-of-dlc)\n",
    "- PAI-EAS：部署推理服务，详细计费说明请参考[PAI-EAS 计费说明](https://help.aliyun.com/zh/pai/product-overview/billing-of-eas)\n",
    "- OSS：存储训练任务输出的模型、训练代码、TensorBoard 日志等，详细计费说明请参考[OSS 计费概述](https://help.aliyun.com/zh/oss/product-overview/billing-overview)\n",
    "\n",
    "> 通过参与云产品免费试用，使用**指定资源机型**提交训练作业或是部署推理服务，可以免费试用 PAI 产品，具体请参考[PAI 免费试用](https://help.aliyun.com/zh/pai/product-overview/free-quota-for-new-users)。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 安装和配置 SDK\n",
    "\n",
    "我们需要首先安装 PAI Python SDK 以运行本示例。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# !python -m pip uninstall alipai\n",
    "# !python -m pip install --upgrade git+https://github.com/aliyun/pai-python-sdk.git@dev/dsw_cred\n",
    "\n",
    "# 用于调用部署的LLM模型服务\n",
    "!python -m pip install openai"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "SDK 需要配置访问阿里云服务需要的 AccessKey，以及当前使用的工作空间和 OSS Bucket。在 PAI SDK 安装之后，通过在**命令行终端** 中执行以下命令，按照引导配置密钥、工作空间等信息。\n",
    "\n",
    "```shell\n",
    "\n",
    "# 以下命令，请在 命令行终端 中执行.\n",
    "\n",
    "python -m pai.toolkit.config\n",
    "\n",
    "```\n",
    "\n",
    "我们可以通过以下代码验证配置是否已生效。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "hide-output"
    ]
   },
   "outputs": [],
   "source": [
    "import pai\n",
    "from pai.session import get_default_session, setup_default_session\n",
    "\n",
    "print(pai.__version__)\n",
    "\n",
    "\n",
    "sess = get_default_session()\n",
    "\n",
    "# 你也可以通过代码方式配置AK/SK/Region/WorkspaceId等信息\n",
    "# if not sess:\n",
    "#     sess = setup_default_session(\n",
    "#         access_key_id=\"<your-access-key-id>\",\n",
    "#         access_key_secret=\"<your-access-key-secret>\",\n",
    "#         region_id=\"<region-id>\",\n",
    "#         workspace_id=\"<workspace-id>\",\n",
    "#         oss_bucket_name=\"<oss-bucket-name>\",\n",
    "#     )\n",
    "#     sess.save_config()\n",
    "\n",
    "\n",
    "# 获取配置的工作空间信息\n",
    "assert sess.workspace_name is not None\n",
    "print(sess.workspace_name)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 查看 PAI 提供的预训练模型\n",
    "\n",
    "我们可以通过参数`provider`为`pai`，获取`PAI`公共模型仓库下的模型，其中包含了 PAI 提供的模型和从开源社区精选的模型。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pai.model import RegisteredModel\n",
    "\n",
    "\n",
    "data = [[\"ModelName\", \"Task\", \"Revision\"]]\n",
    "\n",
    "# 获取公共模型仓库'pai'提供的\"大语言\"模型列表\n",
    "for m in RegisteredModel.list(model_provider=\"pai\", task=\"large-language-model\"):\n",
    "    revision = m.version_labels.get(\"revision\")\n",
    "    license = m.version_labels.get(\"license\")\n",
    "    task = m.task\n",
    "    data.append([m.model_name, task, revision])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import HTML, display\n",
    "\n",
    "display(\n",
    "    HTML(\n",
    "        \"<table><tr>{}</tr></table>\".format(\n",
    "            \"</tr><tr>\".join(\n",
    "                \"<td>{}</td>\".format(\"</td><td>\".join(str(_) for _ in row))\n",
    "                for row in data\n",
    "            )\n",
    "        )\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "通过`model_name`和`model_provider`参数，我们可以获取 PAI 提供的预训练模型(`RegisteredModel`对象)，`RegisteredModel`对象包含了模型所在的 OSS Bucket 信息，以及模型的预训练算法配置。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pai.model import RegisteredModel\n",
    "\n",
    "qwen2_model_name = \"qwen2-0.5b-instruct\"\n",
    "\n",
    "# 获取PAI模型仓库中的bert-base-uncased模型\n",
    "qwen2_model = RegisteredModel(\n",
    "    model_name=qwen2_model_name,\n",
    "    model_provider=\"pai\",\n",
    ")\n",
    "\n",
    "\n",
    "# 模型URI路径\n",
    "print(qwen2_model.model_data)\n",
    "\n",
    "# 查看模型的训练算法配置\n",
    "print(qwen2_model.training_spec)\n",
    "\n",
    "# 查看模型的推理配置\n",
    "print(qwen2_model.inference_spec)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 部署和调用模型服务\n",
    "\n",
    "通过PAI提供的推理服务配置模板，可以快速部署模型。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pai.common.utils import random_str\n",
    "\n",
    "\n",
    "# 使用默认配置部署模型\n",
    "predictor = qwen2_model.deploy(service_name=\"qwen2_05b_{}\".format(random_str(6)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "PAI ModelGallery的大语言模型部署的推理服务默认支持以OpenAI API的方式进行调用\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from openai import OpenAI\n",
    "\n",
    "openai_client: OpenAI = predictor.openai()\n",
    "\n",
    "resp = openai_client.chat.completions.create(\n",
    "    model=\"default\",\n",
    "    messages=[\n",
    "        {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n",
    "        {\"role\": \"user\", \"content\": \"Hello!\"},\n",
    "    ],\n",
    ")\n",
    "\n",
    "print(resp)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "测试完成后删除推理服务"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "predictor.delete_service()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型微调训练\n",
    "\n",
    "使用PAI预置的微调训练脚本和镜像，可以轻松通过LoRA、QLoRA或是全参数微调的方式微调Model Gallery中的大语言模型。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pai.model import ModelTrainingRecipe\n",
    "\n",
    "# 查看模型微调配置\n",
    "\n",
    "print(qwen2_model.training_spec)\n",
    "\n",
    "\n",
    "# 初始化LoRA微调方法\n",
    "qwen2_recipe = ModelTrainingRecipe(\n",
    "    model_name=qwen2_model_name,\n",
    "    model_provider=\"pai\",\n",
    "    # 使用LoRA微调训练模型\n",
    "    method=\"LoRA_LLM\",\n",
    "    base_job_name=\"qwen2_0.5b_qlora_finetune\",\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ModelRecipe自带的测试数据集\n",
    "print(qwen2_recipe.default_inputs)\n",
    "\n",
    "# 默认输入数据\n",
    "print(qwen2_recipe.output_channels)\n",
    "\n",
    "# 默认输入超参\n",
    "print(qwen2_recipe.hyperparameters)\n",
    "print(qwen2_recipe.hyperparameter_definitions)\n",
    "\n",
    "# 默认使用机型配置\n",
    "print(qwen2_recipe.instance_type)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用默认测试数据集提交一个训练任务。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "training_job = qwen2_recipe.train(\n",
    "    wait=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在训练结束之后获取产出模型的 OSS Bucket 路径。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(qwen2_recipe.model_data())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通过`train`方法的`inputs`参数，开发者可以使用 OSS 上的数据集对模型进行微调训练：\n",
    "\n",
    "```python\n",
    "\n",
    "training_job = qwen2_recipe.train(\n",
    "    # 通过inputs用户OSS Bucket上的数据\n",
    "    inputs={\n",
    "        \"train\": \"oss://<YourOssBucket/<YourOssPath>\"\n",
    "        \"validation\": \"oss://<YourOssBucket/<YourOssPath>\"\n",
    "    }\n",
    ")\n",
    "\n",
    "```\n",
    "\n",
    "LLM 训练算法使用问答对作为训练数据，默认使用`json`文件，例如以下：\n",
    "\n",
    "```\n",
    "[\n",
    "    {\n",
    "        \"instruction\": \"你是一个心血管科医生，请根据患者的问题给出建议：我患高血压五六年啦，天天喝药吃烦啦，哪种东西能根治高血压，高血压克星是什么？\",\n",
    "        \"output\": \"高血压的患者可以吃许多新鲜的水果蔬菜或者是芹菜山药之类的食物，可以起些降血压的作用，另外高血压的患者平时也应当注意低盐，低脂，低胆固醇饮食，适当的实施体育运动和锻练高血压的患者还应当在医生的指导下口服降血压的药物，断然不可擅自停药，防止对血压引发影响。\"\n",
    "     },\n",
    "    {\n",
    "        \"instruction\": \"你是一个呼吸科医生，请根据患者的问题给出建议：风寒感冒咳白痰怎么治疗？\",\n",
    "        \"output\": \"风寒感冒，咳有白痰的患者，主要症咳嗽痰多色白量多，兼具胸脘作闷，鼻塞，流清涕，全身酸痛，易疲劳等症状。临床诊断上常用杏苏散和二陈丸实施治疗。在用药物治疗的同时，应忌食辛辣刺激性的食物和寒凉性食物，多吃易消化营养丰富的食物，宜清淡饮食，留意歇息。\"\n",
    "     }\n",
    "]\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 注册微调模型\n",
    "\n",
    "使用以下代码注册产出模型到 PAI 的模型仓库，以下代码中，我们直接复用了qwen2-0.5b-instruct的推理服务配置注册模型。后续可以直接基于改部署配置部署模型。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pai.model import Model\n",
    "\n",
    "# 复用已有模型的推理服务配置\n",
    "print(qwen2_model.inference_spec)\n",
    "\n",
    "\n",
    "# 注册新的模型到模型仓库\n",
    "model = Model(\n",
    "    model_data=qwen2_recipe.model_data(),\n",
    "    inference_spec=qwen2_model.inference_spec,\n",
    ")\n",
    "\n",
    "registered_model = model.register(model_name=\"qwen2_finetune\", version=\"0.3.0\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注册的模型可以通过`RegisteredModel`类获取使用。\n",
    "\n",
    "```shell\n",
    "\n",
    "# 查看所有注册模型\n",
    "for m in RegisteredModel.list():\n",
    "\tprint(m)\n",
    "\n",
    "\n",
    "m = RegisteredModel(\n",
    "\tmodel_name=\"qwen2_finetune\",\n",
    "\tmodel_version=\"0.1.0\",\n",
    ")\n",
    "\n",
    "# 查看模型路径\n",
    "print(m.model_data)\n",
    "\n",
    "# 使用模型推理服务配置部署推理服务\n",
    "\n",
    "predictor = m.deploy(service_name=\"<service_name>\")\n",
    "\n",
    "\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 部署和调用微调模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pai.predictor import Predictor\n",
    "from pai.common.utils import random_str\n",
    "\n",
    "\n",
    "finetune_model = RegisteredModel(\n",
    "    model_name=\"qwen2_finetune\",\n",
    "    model_version=\"0.3.0\",\n",
    ")\n",
    "\n",
    "predictor = finetune_model.deploy(\n",
    "    service_name=\"qwen2_finetune_{}\".format(random_str(6))\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通过OpenAI SDK调用推理服务模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from openai import OpenAI\n",
    "\n",
    "openai_client: OpenAI = predictor.openai()\n",
    "\n",
    "\n",
    "resp = openai_client.chat.completions.create(\n",
    "    model=\"default\",\n",
    "    messages=[\n",
    "        {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n",
    "        {\"role\": \"user\", \"content\": \"Hello!\"},\n",
    "    ],\n",
    ")\n",
    "\n",
    "print(resp)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "测试完成后删除推理服务，释放机器资源\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "predictor.delete_service()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.19"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
